<template>
  <!-- 视频上传容器 -->
  <div class="upload-container">
    <!-- Element Plus 上传组件，支持拖拽 -->
    <el-upload
      class="upload-area"
      drag
      action="#"
      :auto-upload="false"
      :on-change="handleFileChange"
      :show-file-list="false"
    >
      <!-- 上传图标 -->
      <el-icon class="el-icon--upload">
        <upload-filled />
      </el-icon>
      <!-- 上传提示文字 -->
      <div class="el-upload__text">将文件拖到此处，或<em>点击上传</em></div>
      <!-- 上传提示信息 -->
      <template #tip>
        <div class="el-upload__tip">
          支持上传 mp4、mov、avi 格式视频文件，单个文件不超过 2GB
        </div>
      </template>
    </el-upload>

    <!-- 当有文件在文件列表中时显示文件列表 -->
    <div class="file-list" v-if="fileList.length > 0">
      <!-- 遍历文件列表，显示每个文件的信息 -->
      <div v-for="(file, index) in fileList" :key="index" class="file-item">
        <!-- 文件基本信息 -->
        <div class="file-info">
          <!-- 文件名称 -->
          <span class="file-name">{{ file.fileName }}</span>
          <!-- 文件大小 -->
          <span class="file-size">{{ formatFileSize(file.fileSize) }}</span>
        </div>

        <!-- 上传进度条 -->
        <div class="upload-progress">
          <!-- 根据文件状态显示不同状态的进度条 -->
          <el-progress
            :percentage="file.progress"
            :status="
              file.fileStatus === FileStatus.Success
                ? 'success'
                : file.fileStatus === FileStatus.Failed
                ? 'exception'
                : ''
            "
          />
        </div>

        <!-- 文件操作按钮 -->
        <div class="file-actions">
          <!-- 当文件状态为等待上传时显示开始上传按钮 -->
          <el-button
            v-if="file.fileStatus === FileStatus.Waiting"
            type="primary"
            size="small"
            @click="handleUpload(index)"
          >
            开始上传
          </el-button>
          <!-- 当文件状态为上传中时显示取消按钮 -->
          <el-button
            v-if="file.fileStatus === FileStatus.Uploading"
            type="danger"
            size="small"
            @click="handleCancel(index)"
          >
            取消
          </el-button>
          <!-- 当文件状态为上传失败时显示重试按钮 -->
          <el-button
            v-if="file.fileStatus === FileStatus.Failed"
            type="primary"
            size="small"
            @click="handleRetry(index)"
          >
            重试
          </el-button>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from "vue";
import { ElMessage } from "element-plus";
import { UploadFilled } from "@element-plus/icons-vue";
// 引入 SparkMD5 库，用于计算文件的 MD5 值
import SparkMD5 from "spark-md5";
// 引入 axios 库，用于发起 HTTP 请求
import axios from "axios";

// 并发控制配置
// 最大并发数，同时上传的切片数量上限
const CONCURRENT_LIMIT = 3;
// 重试次数，当切片上传失败时的重试次数
const RETRY_TIMES = 3;
// 重试延迟，每次重试的时间间隔（毫秒）
const RETRY_DELAY = 1000;

// 上传队列，存储待上传的切片任务
const uploadQueue = ref([]);
// 当前正在上传的切片数量
const activeUploads = ref(0);
// 记录每个切片的上传状态，使用 Map 存储
const chunkStatus = ref(new Map());

// 文件状态枚举，定义文件的不同上传状态
const FileStatus = {
  Uploading: "uploading",
  Success: "success",
  Failed: "failed",
  Waiting: "waiting", // 添加等待状态
};

// 文件列表，存储待上传的文件信息
const fileList = ref([]);

// 文件类型和大小限制
// 允许上传的文件类型
const FILE_TYPE_LIMIT = ["video/mp4", "video/avi", "video/quicktime"];
// 文件大小限制，单位为字节，这里是 1GB
const FILE_SIZE_LIMIT = 1024 * 1024 * 1024;

// 检查已上传的切片
const checkUploadedChunks = async (fileMd5, fileName) => {
  try {
    const { data } = await axios({
      method: "post",
      url: "http://localhost:3001/api/check-chunks",
      data: {
        md5: fileMd5,
        fileName: fileName,
      },
      headers: {
        "Content-Type": "application/json",
      },
    });
    return data.uploadedChunks || [];
  } catch (error) {
    console.error("检查已上传切片失败:", error);
    return [];
  }
};

// 处理文件选择事件
const handleFileChange = async (file) => {
  try {
    // 校验文件类型和大小
    if (!FILE_TYPE_LIMIT.includes(file.raw.type)) {
      // 文件类型不支持，显示错误提示
      ElMessage.error("不支持的文件类型");
      return;
    }
    if (file.raw.size > FILE_SIZE_LIMIT) {
      // 文件大小超出限制，显示错误提示
      ElMessage.error("文件大小超出限制");
      return;
    }

    // 添加到文件列表
    fileList.value.push({
      originFile: file.raw,
      fileName: file.raw.name,
      fileSize: file.raw.size,
      fileStatus: FileStatus.Waiting, // 初始状态为等待
      progress: 0,
    });

    // 开始上传大文件
    await handleUploadLargeFile(file.raw, fileList.value.length - 1);
  } catch (error) {
    // 文件处理出错，记录错误日志并显示错误提示
    console.error("文件处理错误:", error);
    ElMessage.error("文件处理失败");
  }
};

// 处理大文件上传
const handleUploadLargeFile = async (file, index) => {
  try {
    // 生成文件切片
    const chunks = generateFileChunks(file);

    // 计算文件 MD5
    const fileMd5 = await calculateFileMD5(file);

    // 检查文件是否已存在（秒传）
    const { data: checkData } = await axios({
      method: "post",
      url: "http://localhost:3001/api/check-file",
      data: {
        md5: fileMd5,
        fileName: file.name,
        fileSize: file.size,
      },
      headers: {
        "Content-Type": "application/json",
      },
    });

    if (checkData.state === 1) {
      // 秒传成功，更新文件状态和进度，并显示成功提示
      fileList.value[index].fileStatus = FileStatus.Success;
      fileList.value[index].progress = 100;
      ElMessage.success("文件秒传成功");
      return;
    }

    // 检查已上传的切片
    const uploadedChunks = await checkUploadedChunks(fileMd5, file.name);
    
    // 初始化切片状态
    chunkStatus.value = new Map();
    chunks.forEach((_, i) => {
      const isUploaded = uploadedChunks.includes(i);
      chunkStatus.value.set(i, { 
        status: isUploaded ? "success" : "pending", 
        retryCount: 0 
      });
    });

    // 创建上传任务（只包含未上传的切片）
    const uploadTasks = chunks
      .map((chunk, i) => ({
        chunk,
        index: i,
        fileMd5,
        fileName: file.name,
        fileIndex: index,
      }))
      .filter((_, i) => !uploadedChunks.includes(i));

    // 将任务添加到队列
    uploadQueue.value = uploadTasks;
    
    // 更新文件状态为上传中
    fileList.value[index].fileStatus = FileStatus.Uploading;
    
    // 计算初始进度
    const initialProgress = Math.floor((uploadedChunks.length / chunks.length) * 100);
    fileList.value[index].progress = initialProgress;

    await processUploadQueue();
  } catch (error) {
    console.error("上传错误:", error);
    fileList.value[index].fileStatus = FileStatus.Failed;
    ElMessage.error("文件上传失败");
  }
};

// 生成文件切片
const generateFileChunks = (file) => {
  // 每个切片的大小，这里是 2MB
  const chunkSize = 2 * 1024 * 1024;
  const chunks = [];
  let cur = 0;

  while (cur < file.size) {
    // 从文件中截取切片
    chunks.push(file.slice(cur, cur + chunkSize));
    cur += chunkSize;
  }

  return chunks;
};

// 计算文件 MD5
const calculateFileMD5 = (file) => {
  return new Promise((resolve, reject) => {
    // 创建文件读取器
    const reader = new FileReader();
    // 以 ArrayBuffer 格式读取文件
    reader.readAsArrayBuffer(file);

    reader.onload = (e) => {
      // 获取文件的 ArrayBuffer
      const buffer = e.target.result;
      // 创建 SparkMD5 实例
      const spark = new SparkMD5.ArrayBuffer();
      // 追加文件内容到 SparkMD5 实例
      spark.append(buffer);
      // 计算 MD5 值
      const md5 = spark.end();
      // 解析 MD5 值
      resolve(md5);
    };

    reader.onerror = (error) => {
      // 读取文件出错，拒绝 Promise
      reject(error);
    };
  });
};

// 取消上传
const handleCancel = (index) => {
  // 更新文件状态为上传失败，进度重置为 0
  fileList.value[index].fileStatus = FileStatus.Failed;
  fileList.value[index].progress = 0;
};

// 重试上传
const handleRetry = async (index) => {
  const file = fileList.value[index].originFile;
  // 更新文件状态为上传中，进度重置为 0
  fileList.value[index].fileStatus = FileStatus.Uploading;
  fileList.value[index].progress = 0;
  // 重新开始上传大文件
  await handleUploadLargeFile(file, index);
};

// 格式化文件大小
const formatFileSize = (size) => {
  if (size < 1024) {
    return size + "B";
  } else if (size < 1024 * 1024) {
    return (size / 1024).toFixed(2) + "KB";
  } else if (size < 1024 * 1024 * 1024) {
    return (size / (1024 * 1024)).toFixed(2) + "MB";
  } else {
    return (size / (1024 * 1024 * 1024)).toFixed(2) + "GB";
  }
};

// 处理上传队列
const processUploadQueue = async () => {
  while (
    uploadQueue.value.length > 0 &&
    activeUploads.value < CONCURRENT_LIMIT
  ) {
    // 从队列中取出一个任务
    const task = uploadQueue.value.shift();
    // 增加当前正在上传的任务数量
    activeUploads.value++;
    // 上传切片
    await uploadChunk(task);
  }
};

// 上传单个切片
const uploadChunk = async (task) => {
  const { chunk, index, fileMd5, fileName, fileIndex } = task;
  const chunkInfo = chunkStatus.value.get(index);

  try {
    // 创建 FormData 对象，用于上传文件切片
    const formData = new FormData();
    formData.append("file", chunk);
    formData.append("chunkIndex", index);
    formData.append("md5", fileMd5);
    formData.append("fileName", fileName);

    const { data } = await axios({
      method: "post",
      url: "http://localhost:3001/api/upload-chunk",
      data: formData,
      headers: {
        "Content-Type": "multipart/form-data",
      },
      onUploadProgress: (progressEvent) => {
        // 确保文件状态为上传中
        if (fileList.value[fileIndex].fileStatus !== FileStatus.Uploading) {
          fileList.value[fileIndex].fileStatus = FileStatus.Uploading;
        }
        // 更新切片上传进度
        updateChunkProgress(fileIndex, index, progressEvent);
      },
    });

    // 标记当前切片上传成功
    chunkStatus.value.set(index, {
      status: "success",
      retryCount: chunkInfo.retryCount,
    });

    // 检查是否所有切片都上传完成
    const allChunks = Array.from(chunkStatus.value.values());
    const isComplete = allChunks.every((chunk) => chunk.status === "success");

    if (isComplete) {
      // 所有切片上传完成，更新文件状态和进度，并显示成功提示
      fileList.value[fileIndex].fileStatus = FileStatus.Success;
      fileList.value[fileIndex].progress = 100;
      ElMessage.success("文件上传成功");
    }
  } catch (error) {
    // 切片上传失败，记录错误日志
    console.error(`切片 ${index} 上传失败:`, error);

    if (chunkInfo.retryCount < RETRY_TIMES) {
      // 重试上传
      chunkInfo.retryCount++;
      chunkStatus.value.set(index, {
        status: "pending",
        retryCount: chunkInfo.retryCount,
      });
      setTimeout(() => {
        // 将任务重新添加到队列
        uploadQueue.value.push(task);
        // 处理上传队列
        processUploadQueue();
      }, RETRY_DELAY);
    } else {
      // 重试次数用完，标记为失败
      chunkStatus.value.set(index, {
        status: "failed",
        retryCount: chunkInfo.retryCount,
      });
      fileList.value[fileIndex].fileStatus = FileStatus.Failed;
      ElMessage.error(`切片 ${index + 1} 上传失败`);
    }
  } finally {
    // 减少当前正在上传的任务数量
    activeUploads.value--;
    // 继续处理上传队列
    processUploadQueue();
  }
};

// 更新切片上传进度
const updateChunkProgress = (fileIndex, chunkIndex, progressEvent) => {
  const totalChunks = chunkStatus.value.size;
  const chunkProgress = progressEvent.loaded / progressEvent.total;

  // 计算已完成切片的总进度
  const completedChunks = Array.from(chunkStatus.value.values()).filter(
    (status) => status.status === "success"
  ).length;

  // 计算总体进度：已完成切片 + 当前切片进度
  const totalProgress = Math.floor(
    ((completedChunks + chunkProgress) / totalChunks) * 100
  );

  // 计算上传速度（字节/秒）
  const currentTime = Date.now();

  // 更新文件上传进度和速度
  fileList.value[fileIndex].progress = totalProgress;
  fileList.value[fileIndex].lastUpdateTime = currentTime;
  fileList.value[fileIndex].lastLoaded = progressEvent.loaded;
};
</script>

<style scoped>
.upload-container {
  width: 100%;
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.upload-area {
  width: 100%;
}

.file-list {
  margin-top: 20px;
}

.file-item {
  padding: 10px;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  margin-bottom: 10px;
}

.file-info {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}

.file-name {
  font-weight: bold;
}

.upload-progress {
  margin: 10px 0;
}

.file-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
</style>